﻿using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;

namespace Niaofei.Repository
{
    /// <summary>
    /// EFCore 仓储封装实现
    /// </summary>
    public class EFCoreRepository<TDbContext, TEntity> : IRepository<TEntity> where TDbContext : DbContext where TEntity : class, IEntity
    {
        private readonly IDbContextProvider<TDbContext> _dbContextProvider;

        public EFCoreRepository(IDbContextProvider<TDbContext> dbContextProvider)
        {
            _dbContextProvider = dbContextProvider;
        }

        public async Task<DbContext> GetDbContextAsync()
        {
            return await _dbContextProvider.GetDbContextAsync();
        }

        public async Task<DbSet<TEntity>> GetDbSetAsync()
        {
            var dbContext = await GetDbContextAsync();

            return dbContext.Set<TEntity>();
        }

        public async Task<IQueryable<TEntity>> GetQueryableAsync()
        {
            var dbContext = await GetDbContextAsync();

            var dbSet = dbContext.Set<TEntity>();

            if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
            {
                return dbSet.AsQueryable().Where(e => !((ISoftDelete)e).IsDeleted);
            }

            return dbSet.AsQueryable();
        }

        public async Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync();

            if (entity is ISoftDelete soft)
            {
                soft.IsDeleted = true;
                dbContext.Update(soft);
            }
            else
            {
                dbContext.Remove(entity);
            }

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }

        public async Task DeleteAsync(Expression<Func<TEntity, bool>> predicate, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync();
            var dbSet = await GetDbSetAsync();

            var entitys = await dbSet.Where(predicate).ToListAsync(cancellationToken);

            await DeleteManyAsync(entitys, autoSave, cancellationToken);

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }

        public async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync();

            if (entities is IEnumerable<ISoftDelete> softs)
            {
                foreach (ISoftDelete soft in softs)
                {
                    soft.IsDeleted = true;
                }

                dbContext.UpdateRange(entities);
            }
            else
            {
                dbContext.RemoveRange(entities);
            }

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }

        public async Task<TEntity> FindAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = true, CancellationToken cancellationToken = default)
        {
            var query = includeDetails ? await WithDetailsAsync() : await GetQueryableAsync();

            var result = await query.Where(predicate).SingleOrDefaultAsync(cancellationToken);

            return result!;
        }

        public async Task<TEntity?> GetAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = true, CancellationToken cancellationToken = default)
        {
            var query = includeDetails ? await WithDetailsAsync() : await GetQueryableAsync();

            return await query.FirstOrDefaultAsync(predicate, cancellationToken);
        }

        public async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            var query = includeDetails ? await WithDetailsAsync() : await GetQueryableAsync();

            return await query.Where(predicate).ToListAsync(cancellationToken);
        }

        public async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync();
            
            if (entity is ISoftDelete soft)
            {
                soft.IsDeleted = false;
            }

            if(entity is ICreateTime createTimeEntity)
            {
                createTimeEntity.CreateTime = DateTime.Now;
            }

            //Todo:待验证
            entity = dbContext.Add(entity).Entity;

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }

            return entity;
        }

        public async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync();

            foreach (var entity in entities)
            {
                if (entity is ISoftDelete soft)
                {
                    soft.IsDeleted = false;
                }
                if (entity is ICreateTime createTimeEntity)
                {
                    createTimeEntity.CreateTime = DateTime.Now;
                }
            }

            dbContext.AddRange(entities);

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }

        public async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync();
            //Todo:待验证
            entity = dbContext.Update(entity).Entity;

            if (entity is IUpdateTime updateTimeEntity)
            {
                updateTimeEntity.UpdateTime = DateTime.Now;
            }

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }

            return entity;
        }

        public async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var dbContext = await GetDbContextAsync();

            dbContext.UpdateRange(entities);

            foreach (var entity in entities)
            {
                if (entity is IUpdateTime updateTimeEntity)
                {
                    updateTimeEntity.UpdateTime = DateTime.Now;
                }
            }

            if (autoSave)
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }

        public virtual async Task<IQueryable<TEntity>> WithDetailsAsync()
        {
            var query = await GetQueryableAsync();

            return query;
        }
    }
}
